package com.webchat.pgc.service;


import com.webchat.common.bean.APIPageResponseBean;
import com.webchat.common.constants.WebConstant;
import com.webchat.common.enums.ArticleStatusEnum;
import com.webchat.common.enums.RedisKeyEnum;
import com.webchat.common.enums.messagequeue.MessageQueueEnum;
import com.webchat.common.service.RedisService;
import com.webchat.common.service.messagequeue.producer.MessageQueueProducer;
import com.webchat.common.util.JsonUtil;
import com.webchat.domain.dto.queue.ArticleDelayMessageDTO;
import com.webchat.domain.vo.request.publicaccount.SaveArticleRequestVO;
import com.webchat.domain.vo.response.UserBaseResponseInfoVO;
import com.webchat.domain.vo.response.mess.PublicAccountArticleMessageVO;
import com.webchat.domain.vo.response.publicaccount.ArticleBaseResponseVO;
import com.webchat.pgc.repository.dao.IArticleDAO;
import com.webchat.pgc.repository.entity.ArticleEntity;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 公众号文章服务
 *
 */
@Service
public class OfficialArticleService {

    @Autowired
    private IArticleDAO articleDAO;

    @Autowired
    private AccountService accountService;

    @Autowired
    private RedisService redisService;

    @Autowired
    private MessageQueueProducer<ArticleDelayMessageDTO, Long> messageQueueProducer;

    /**
     * 公众号推文
     *
     * @param saveArticleRequest
     * @return
     */
    public Long submit(SaveArticleRequestVO saveArticleRequest) {
        /**
         * 1. 持久化文化到数据库
         */
        ArticleEntity articleEntity = this.convert(saveArticleRequest);
        articleEntity = articleDAO.save(articleEntity);
        /**
         * 2. 缓存文章详情到redis
         */
        this.refreshArticleRedisDetailCache(articleEntity);
        /**
         * 3. 推文：MQ ---> 延迟队列 + 普通列表
         * 《实时推文》：用户未指定推送时间（默认当前时间）
         * 《延迟推送》：用户指定未来时间推送
         */
        final String publicAccount = saveArticleRequest.getPublicAccount();
        final Long articleId = articleEntity.getId();
        final Long pushTime = saveArticleRequest.getPlanPushTime();
        this.doSubmitDelayQueue(publicAccount, articleId, pushTime);
        /**
         * 4. 公众号文章消息数据持久化
         */
        // TODO
        return articleId;
    }


    /**
     * 公众号文章推送计划加入延迟队列
     *
     * @param publicAccount 公众号账号
     * @param articleId     公众号文章
     * @param pushTime      设定的推送时间
     */
    private void doSubmitDelayQueue(String publicAccount, Long articleId, Long pushTime) {
        ArticleDelayMessageDTO message = new ArticleDelayMessageDTO();
        message.setArticleId(articleId);
        message.setPublicAccount(publicAccount);
        // 延迟推文时间(如果未设置发布时间，则立即发布)
        pushTime = pushTime == null ? System.currentTimeMillis() : pushTime;
        message.setTime(pushTime);
        // 提交队列:优先级队列（一级队列）
        messageQueueProducer.prioritySend(MessageQueueEnum.QUEUE_OFFICIAL_ARTICLE_PUSH_MESSAGE, message, pushTime);
    }


    /**
     * 刷新公众号文章redis缓存
     *
     * @param articleId
     * @return
     */
    private ArticleBaseResponseVO refreshArticleRedisDetailCache(Long articleId) {
        if (articleId == null) {
            return null;
        }
        ArticleEntity articleEntity = articleDAO.findById(articleId).orElse(null);
        return this.refreshArticleRedisDetailCache(articleEntity);
    }

    /**
     * 刷新公众号文章redis缓存
     *
     * @param articleEntity
     * @return
     */
    private ArticleBaseResponseVO refreshArticleRedisDetailCache(ArticleEntity articleEntity) {
        if (articleEntity == null) {
            return null;
        }
        String cacheKey = RedisKeyEnum.ARTICLE_DETAIL_CACHE.getKey(String.valueOf(articleEntity.getId()));
        // 文章详情我们使用string类型来缓存，每个文章有自己的失效时间，避免缓存雪崩
        ArticleBaseResponseVO articleBaseResponseVO = this.convert(articleEntity);
        redisService.set(cacheKey, JsonUtil.toJsonString(articleBaseResponseVO), RedisKeyEnum.ARTICLE_DETAIL_CACHE.getExpireTime());
        return articleBaseResponseVO;
    }

    /**
     * 缓存空值，防止缓存击穿
     *
     * @param articleId
     */
    private void refreshArticleNoneCache(Long articleId) {
        String cacheKey = RedisKeyEnum.ARTICLE_DETAIL_CACHE.getKey(String.valueOf(articleId));
        // 文章详情我们使用string类型来缓存，每个文章有自己的失效时间，避免缓存雪崩
        redisService.set(cacheKey, WebConstant.CACHE_NONE, RedisKeyEnum.ARTICLE_DETAIL_CACHE.getExpireTime());
    }

    /**
     * 浏览文章
     *
     * @param articleId
     * @return
     */
    public ArticleBaseResponseVO viewArticle(Long articleId) {
        ArticleBaseResponseVO article = this.getArticleDetailFromCache(articleId);
        if (article == null) {
            return null;
        }
        // 设置公众号信息
        article.setPublicAccountInfo(accountService.accountInfo(article.getPublicAccount()));
        return article;
    }

    /**
     * 查询消息列表所有文章信息
     *
     * @param articleId
     * @return
     */
    public PublicAccountArticleMessageVO getPublicAccountArticleMessage(Long articleId) {
        ArticleBaseResponseVO article = this.getArticleDetailFromCache(articleId);
        if (article == null) {
            return null;
        }
        PublicAccountArticleMessageVO publicAccountArticleMessage = new PublicAccountArticleMessageVO();
        publicAccountArticleMessage.setTitle(article.getTitle());
        publicAccountArticleMessage.setDescription(article.getDescription());
        publicAccountArticleMessage.setCover(article.getCover());
        publicAccountArticleMessage.setArticleId(articleId);
        publicAccountArticleMessage.setRedirectUrl(article.getRedirectUrl());
        return publicAccountArticleMessage;
    }


    public ArticleBaseResponseVO getArticleDetailFromCache(Long articleId, boolean needContent) {
        ArticleBaseResponseVO articleVO = this.getArticleDetailFromCache(articleId);
        if (articleVO == null) {
            return null;
        }
        if (!needContent) {
            articleVO.setContent(null);
        }
        return articleVO;
    }

    /**
     * 查询公众号文章详情
     *
     * @param articleId
     * @return
     */
    public ArticleBaseResponseVO getArticleDetailFromCache(Long articleId) {

        String cacheKey = RedisKeyEnum.ARTICLE_DETAIL_CACHE.getKey(String.valueOf(articleId));
        String cache = redisService.get(cacheKey);
        // 这里可能存在击穿问题（比如：有人恶意那不存在的文章一致查询）
        // 文章缓存击穿解决办法：我们缓存一个空值
        if (StringUtils.isNotBlank(cache)) {
            if (WebConstant.CACHE_NONE.equals(cache)) {
                // 文章不存在，直接返回null，不需要在查库
                return null;
            }
            return JsonUtil.fromJson(cache, ArticleBaseResponseVO.class);
        }
        // 缓存不存在主动查询数据库，重新刷新缓存
        ArticleBaseResponseVO articleBase = this.refreshArticleRedisDetailCache(articleId);
        if (articleBase == null) {
            // 数据库文章不存在，这里缓存空值，防止redis击穿
            this.refreshArticleNoneCache(articleId);
        }
        return articleBase;
    }

    /**
     * 批量查询redis，获取文章详情缓存
     *
     * 场景：公众号详情页，一次可能查询10篇文章
     * @param articleIdList
     * @return
     */
    public Map<Long, ArticleBaseResponseVO> batchGetArticleDetailFromCache(List<Long> articleIdList) {
        if (CollectionUtils.isEmpty(articleIdList)) {
            return Collections.emptyMap();
        }
        Map<Long, ArticleBaseResponseVO> batchGetResult = new HashMap<>();
        // 构造批量查询redis的缓存key
        List<String> cacheKeys = articleIdList.stream().map(
                        id -> RedisKeyEnum.ARTICLE_DETAIL_CACHE.getKey(String.valueOf(id)))
                .collect(Collectors.toList());
        // 批量查询redis
        List<String> caches = redisService.mget(cacheKeys);
        for (int i = 0; i < articleIdList.size(); i++) {
            Long articleId = articleIdList.get(i);
            String cache = caches.get(i);
            ArticleBaseResponseVO articleBaseResponseVO;
            if (StringUtils.isNotBlank(cache)) {
                articleBaseResponseVO = JsonUtil.fromJson(cache, ArticleBaseResponseVO.class);
            } else {
                articleBaseResponseVO = this.refreshArticleRedisDetailCache(articleId);
            }
            batchGetResult.put(articleId, articleBaseResponseVO);
        }
        return batchGetResult;
    }

    /**
     * 分页查询文章列表
     *
     * @param pageNo
     * @param pageSize
     * @return
     */
    public APIPageResponseBean<ArticleBaseResponseVO> page(Integer pageNo, Integer pageSize) {
        Pageable pageable = PageRequest.of(pageNo - 1, pageSize, Sort.by(Sort.Direction.DESC, "id"));
        Page<ArticleEntity> userEntities = articleDAO.findAll(pageable);
        List<ArticleBaseResponseVO> articles = new ArrayList<>();
        if (userEntities != null && !CollectionUtils.isEmpty(userEntities.getContent())) {
            // 走缓存批量查询公众号信息
            Set<String> publicAccounts = userEntities.stream().map(ArticleEntity::getPublicAccount).collect(Collectors.toSet());
            Map<String, UserBaseResponseInfoVO> accounts = accountService.batchGet(publicAccounts);
            // 批量构造返回文章列表参数
            articles = userEntities.getContent().stream().map(a -> {
                ArticleBaseResponseVO article = convert(a);
                // 列表一般不需要返回详情信息，减少网络数据包传输设置为null
                article.setContent(null);
                // 这里偷懒了，建议
                article.setPublicAccountInfo(accounts.get(article.getPublicAccount()));
                return article;
            }).collect(Collectors.toList());
        }
        return APIPageResponseBean.success(pageNo, pageSize, userEntities.getTotalElements(), articles);

    }

    private ArticleBaseResponseVO convert(ArticleEntity articleEntity) {
        ArticleBaseResponseVO articleBase = new ArticleBaseResponseVO();
        BeanUtils.copyProperties(articleEntity, articleBase);
        if (articleEntity.getPlanPushDate() != null) {
            articleBase.setPlanPushTime(articleEntity.getPlanPushDate().getTime());
        }
        if (articleEntity.getCreateDate() != null) {
            articleBase.setPublishTime(articleEntity.getCreateDate().getTime());
        }
        return articleBase;
    }

    private ArticleEntity convert(SaveArticleRequestVO saveArticleRequest) {
        Long articleId = saveArticleRequest.getId();
        String author = saveArticleRequest.getAuthor();
        Date now = new Date();
        ArticleEntity articleEntity;
        if (articleId != null) {
            articleEntity = articleDAO.findById(articleId).orElse(null);
            Assert.notNull(articleEntity, "文章更新失败: 文章不存在！");
            Assert.isTrue(ObjectUtils.equals(articleEntity.getAuthor(), author), "没有更新权限！");
        } else {
            articleEntity = new ArticleEntity();
            articleEntity.setCreateDate(now);
            articleEntity.setCreateBy(author);
            articleEntity.setAuthor(author);
            articleEntity.setStatus(ArticleStatusEnum.WAIT_PUSH.getStatus());
        }
        articleEntity.setRedirectUrl(saveArticleRequest.getRedirectUrl());
        articleEntity.setStatus(articleEntity.getStatus());
        articleEntity.setCover(saveArticleRequest.getCover());
        articleEntity.setDescription(saveArticleRequest.getDescription());
        articleEntity.setTitle(saveArticleRequest.getTitle());
        articleEntity.setContent(saveArticleRequest.getContent());
        articleEntity.setPublicAccount(saveArticleRequest.getPublicAccount());
        articleEntity.setSigns(saveArticleRequest.getSigns());
        Date planPushDate = new Date();
        if (saveArticleRequest.getPlanPushTime() != null) {
            planPushDate = new Date(saveArticleRequest.getPlanPushTime());
        }
        articleEntity.setPlanPushDate(planPushDate);
        return articleEntity;
    }
}
